#pragma once
#include <iostream>
#include <string>
#include <vector>
#include <unordered_map>

#include "Log.hpp"
#include "FileUtil.hpp"

namespace IndexArea
{
    using namespace LogArea;
    // 文档相关内容

#define SEP "\3"
#define X 10
#define Y 1

    struct DocInfo
    {
        std::string title;   // 文档标题
        std::string content; // 文档去标签后的内容
        std::string url;     // 官网文档的 url
        uint64_t doc_id;     // 文档id
    };

    // 倒排索引
    typedef struct InvertedIndex
    {
        uint64_t doc_id;  // 文档id
        std::string word; // 关键词
        int weight;       // 关键词权重
    } InvertedIndex;
    typedef std::vector<InvertedIndex> InvertedIndexList;

    // 词频统计
    struct WordCount
    {
        size_t title_count;
        size_t content_count;

        WordCount() : title_count(0), content_count(0)
        {
        }
    };

    class Index
    {
    public:
        static Index *GetInstance()
        {
            // 可以防止一瞬间大量的加锁申请
            if (nullptr == _instance)
            {
                // 加锁
                MyMutexArea::LockGuard lock(_mutex);
                if (nullptr == _instance)
                    _instance = new Index();
            }
            return _instance;
        }

        // 正排索引: 根据文档id找到文档内容
        DocInfo *GetForwardIndex(uint64_t doc_id)
        {
            if (doc_id >= forward_index.size() || doc_id < 0)
            {
                LogArea::LOG(LogLevel::ERROR) << "doc_id error: out of range";
                return nullptr;
            }

            return &forward_index[doc_id];
        }

        // 倒排索引: 根据关键字查找相关的所有文档,获取倒排拉链
        InvertedIndexList *GetInverIndex(const std::string &word)
        {
            auto it = inver_index.find(word);
            if (it == inver_index.end())
            {
                LogArea::LOG(LogLevel::ERROR) << "word error: not found word";
                return nullptr;
            }

            return &(it->second);
        }

        // 建立索引: input 就是存储去标签后的文档的文件路径
        bool BuildIndex(const std::string &input)
        {
            LogArea::LOG(LogLevel::DEBUG)<<"BuildIndex ing...";
            // 以二进制形式打开input文件
            std::ifstream in(input, std::ios::in | std::ios::binary);
            if (!in.is_open())
            {
                LogArea::LOG(LogLevel::ERROR) << "file open error";
                return false;
            }

            // 读取每个文档
            std::string line;
            int count = 0;
            while (std::getline(in, line))
            {
                DocInfo *doc = BuildForwardIndex(line); // 构建正排索引
                if (nullptr == doc)
                {
                    LogArea::LOG(LogLevel::ERROR) << "BulidForwardIndex error";
                    return false;
                }
                BuildInverIndex(*doc);
                count++;
                if(count%50==0)
                    std::cout<<count<<std::endl;
            }
            return true;
        }

    private:
        // 构建正排索引
        DocInfo *BuildForwardIndex(const std::string &line)
        {
            std::vector<std::string> results;
            UtilArea::StringUtil::Split(line, results, SEP);
            if (results.size() != 3) // 分割后必须是 title content url 组成的三个部分
            {
                LogArea::LOG(LogLevel::ERROR) << "CutString error";
                return nullptr;
            }

            DocInfo doc;
            doc.title = results[0];
            doc.content = results[1];
            doc.url = results[2];
            doc.doc_id = forward_index.size();
            forward_index.emplace_back(doc);

            return &forward_index.back();
        }

        // 构建倒排索引
        void BuildInverIndex(const DocInfo &doc)
        {
            // 对标题进行分词
            std::vector<std::string> title_words;
            UtilArea::JiebaUtil::CutString(doc.title, title_words);

            // 统计标题中每个分词的词频
            for (auto &word : title_words)
            {
                // 关键词搜索时不区分大小写,统一转为小写
                boost::to_lower(word);
                word_map[word].title_count++;
            }

            // 对内容进行分词
            std::vector<std::string> content_words;
            UtilArea::JiebaUtil::CutString(doc.content, content_words);

            // 统计内容中每个分词的词频
            for (auto &word : title_words)
            {
                // 关键词搜索时不区分大小写,统一转为小写
                boost::to_lower(word);
                word_map[word].content_count++;
            }

            // 获取 [关键词,词频] 的 pair
            for (auto &word_pair : word_map)
            {
                // 构建倒排关键词映射的节点条的节点,并设置属性
                InvertedIndex elem;
                elem.doc_id = doc.doc_id;
                elem.word = word_pair.first;
                elem.weight = X * word_pair.second.title_count + Y * word_pair.second.content_count;

                // 拿到关键词映射的那条倒排节点条,插入节点
                inver_index[word_pair.first].emplace_back(elem);
            }
        }

    private:
        Index() {}
        Index(const Index &) = delete;
        Index &operator=(const Index &) = delete;

    private:
        std::vector<DocInfo> forward_index; // 正排索引
        // 倒排索引 key:关键词 --> InvertedIndexList:文档id 关键词word 词频count 的映射,一个关键词可能映射多个文档
        std::unordered_map<std::string, InvertedIndexList> inver_index;
        std::unordered_map<std::string, WordCount> word_map; // 用来存储 关键词 --> 词频 的映射关系

        // 设置单例模式
        static Index *_instance;
        static MyMutexArea::MyMutex _mutex;
    };

    Index *Index::_instance = nullptr;
    MyMutexArea::MyMutex Index::_mutex;
}